#!/bin/bash
# rmdirtrash - `rmdir` compatible layer for `trash`
# Version 1.13 (build 20190331)
#
#
# SHORT DESCRIPTION:
#   Put empty directories in trash using the `trash-put` command in a way
#   that is, otherwise as `trash-put` itself, compatible to GNUs `rmdir`.
#
#
# DEPENDENCIES:
#   - `trash` or `trash-put`, provided by the package `trash-cli`
#
#   Note that there are many more dependencies. Nearly every distribution meets
#   those dependencies by default, so they are not listed here. Please also
#   note that this script uses options of POSIX commands, which are not part of
#   the POSIX standard. The extended variants provided by GNU are recommended.
#
#   This script was tested with Ubuntu 10.04 LTS (Lucid Lynx) and
#   Ubuntu 12.04 LTS (Precise Pangolin) only. It SHOULD work great with any
#   other distribution.
#
#   You want to make rmtrash work with your favorite distribution?
#   Go on, I appreciate it!
#
#
# EXIT CODES:
#   An exit status of zero indicates success, a nonzero value indicates the
#   occurence of an error. The following exit codes are fatal, rmtrash stops
#   execution.
#
#      1  unknown error
#      2  invalid options
#      4  requirements of this layer weren't met
#         (`trash-put` and/or `rm` wasn't found, is not installed or
#          is not executable)
#
#   The following exit codes are non-fatal, thus rmtrash aborted execution of
#   the corresponding argument only. All other arguments (prior and posterior
#   the failed argument) will be handled regularly. All following exit codes
#   are bitmasks.
#
#      8  `trash-put` returned a nonzero exit status
#     16  `rm` returned a nonzero exit status
#     32  user interaction required in non-interactive mode
#     64  cannot remove . or ..
#    128  no such file or directory
#    256  not a directory
#    512  directory not empty
#   1024  unable to create trashcan: permission denied
#   2048  unable to trash the trashcan
#   4096  user root isn't allowed to trash files
#
#
# KNOWN BUGS:
#   If you use rmtrash as an bash alias, you maybe noticed, that the alias
#   doesn't work when using sudo. You can catch up on that by adding
#       alias sudo='sudo '
#   to the bashrc. Note the space before the closing quote. Consider the
#   manpage of bash:
#       "A trailing space in  value causes the next word to be checked for
#       alias substitution when the alias is expanded."
#
#
# BUGS:
#   Please report bugs using GitHub's issue tracker at
#   <https://github.com/PhrozenByte/rmtrash>.
#
#
# COPYRIGHT AND LICENSING:
#   Copyright (C) 2011-2019  Daniel Rudolf <https://www.daniel-rudolf.de/>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, version 3 of the License only.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
# SEE ALSO:
#   trash(1), trash-put(1), trash-list(1), list-trash(1), trash-empty(1),
#   empty-trash(1), trash-restore(1), trash-rm(1), the trash-cli project at
#   <https://github.com/andreafrancia/trash-cli>, the FreeDesktop.org Trash
#   Specification at <http://www.ramendik.ru/docs/trashspec.html> and the
#   rmtrash project at <https://github.com/PhrozenByte/rmtrash>
#
#
# CHANGELOG:
#   v1.1 - 2011-09-11 00:35:00+0200
#       * printing errors with relative, not absolute paths
#       * cleaning up the code
#   v1.2 - 2011-09-12 22:08:00+0200
#       + adding replacement options --forbid-root and --forbid-root-force
#       * fixing unexpected behaviour of filenames with special characters
#         Many thanks to ubuntuusers.de member Lasall!
#       * improving code styling
#   v1.3 - 2011-09-13 16:17:00+0200
#       * fixing filenames with special characters when using --parents
#   v1.4 - 2012-12-11 23:47:00+0100
#       + detecting the renaming of `trash` to `trash-put`
#       + reading paths of `rm` and `trash-put` from $PATH
#       + checking for executability of `rm` and `trash-put`
#       * minor code improvements
#   v1.5 - no changes
#   v1.6 - 2013-08-07 14:57:00+0200
#       + adapting some new features of rmtrash 1.4
#       * minor code improvements
#   v1.7 - 2013-11-29 22:54:00+0100
#       + adding function showUsage()
#       * moving --help and --version handling
#       * improving documentation
#   v1.8 - 2014-06-01 23:05:00+0200
#       + add VERSION and BUILD variables
#       * change --forbid-root handling
#       * remove dpkg
#   v1.9 - 2015-02-12 11:50:00+0100
#       * documentation update
#       * code cleanup
#   v1.10 - 2015-03-24 00:40:00+0100
#       * updating some output messages
#   v1.11 - 2015-05-11 00:05:00+0200
#       * fixing deletion of invalid symlinks
#       * unifying quotations
#   v1.12 - 2017-10-18 18:07:00+0200
#       * fixing GitHub Issue #8
#   v1.13 - 2019-03-31 13:15:00+0200
#       * fixing GitHub Issue #10

LC_ALL=C
APP_NAME=$(basename "$0")

VERSION="1.13"
BUILD="20190331"

function showUsage() {
	echo "Usage:"
	echo "  $APP_NAME [OPTION]... DIRECTORY..."
}

function getOptionsAsCmdString() {
	local CMD="$1"

	if [ $IGNORE_FAIL_ON_NON_EMPTY == true ]; then
		CMD+=" --ignore-fail-on-non-empty"
	fi
	if [ $PARENTS == true ]; then
		CMD+=" --parents"
	fi
	if [ $VERBOSE == true ]; then
		CMD+=" --verbose"
	fi

	echo "$CMD"
}

# get path of rmdir
RMDIR_CMD="$(which "rmdir")"

# check if rmdir is installed
if [ -z "$RMDIR_CMD" ]; then
	echo "$APP_NAME: command \`rmdir\` was not found." >&2
	exit 4
fi

# check if rmdir is executable
if [ ! -x "$RMDIR_CMD" ]; then
	echo "$APP_NAME: \`$RMDIR_CMD\` is not executable." >&2
	exit 4
fi

# get path of trash
# the path depends on the installed version of the trash-cli package
TRASH_CMD="$(which "trash-put")"
if [ -z "$TRASH_CMD" ]; then
	TRASH_CMD="$(which "trash")"
fi

# check if trasgh-cli is installed
if [ -z "$TRASH_CMD" ]; then
	echo "$APP_NAME: command \`trash-put\` was not found." >&2
	echo "This program requires a command line interface trashcan utility." >&2
	echo "It seems that the required program is not installed yet." >&2
	exit 4
fi

# check if trash is executable
if [ ! -x "$TRASH_CMD" ]; then
	echo "$APP_NAME: \`$TRASH_CMD\` is not executable." >&2
	exit 4
fi

# check if shell is running in interactive mode
SHELL_IN_INTERACTIVE_MODE=false
tty -s
if [ $? -eq 0 ]; then
	SHELL_IN_INTERACTIVE_MODE=true
fi

# use getopt to parse parameters
if ! OPTIONS=$(getopt -n "$APP_NAME" -o pv -l "ignore-fail-on-non-empty" -l "parents" -l "verbose" -l "forbid-root::" -l "forbid-root-force" -l "help" -l "version" -- "$@"); then
	showUsage
	exit 2
fi
eval set -- "${OPTIONS}"

# default option values
IGNORE_FAIL_ON_NON_EMPTY=false	# --ignore-fail-on-non-empty    bool
PARENTS=false					# -p / --parents                bool
VERBOSE=false					# -v / --verbose                bool
FORBID_ROOT="never"				# --forbid-root                 string ( always / ask-forbid / ask-pass / pass / never )

# parse options
while true; do
	case "$1" in
		"--ignore-fail-on-non-empty")
			IGNORE_FAIL_ON_NON_EMPTY=true
			shift
			;;

		"-p"|"--parents")
			PARENTS=true
			shift
			;;

		"-v"|"--verbose")
			VERBOSE=true
			shift
			;;

		"--forbid-root")
			if [ "$2" == "always" ] || [ "$2" == "yes" ]; then
				FORBID_ROOT="always"
			elif [ "$2" == "ask-forbid" ] || [ -z "$2" ]; then
				FORBID_ROOT="ask-forbid"
			elif [ "$2" == "ask-pass" ]; then
				FORBID_ROOT="ask-pass"
			elif [ "$2" == "pass" ]; then
				FORBID_ROOT="pass"
			elif [ "$2" == "never" ] || [ "$2" == "no" ]; then
				FORBID_ROOT="never"
			else
				echo "$APP_NAME: invalid argument '$2' for '--forbid-root'" >&2
				echo "Valid arguments are:" >&2
				echo "  - 'always', 'yes'" >&2
				echo "  - 'ask-forbid'" >&2
				echo "  - 'ask-pass'" >&2
				echo "  - 'pass'" >&2
				echo "  - 'never', 'no'" >&2
				echo "Try \`$APP_NAME --help\` for more information." >&2
				exit 2
			fi

			shift 2
			;;

		"--forbid-root-force")
			# backward compatibility
			FORBID_ROOT="pass"
			shift
			;;

		"--help")
			TRASH_CMD_NAME="$(basename "$TRASH_CMD")"

			showUsage
			echo
			echo "Put empty directories in trash using the \`$TRASH_CMD_NAME\` command in a way that is,"
			echo "otherwise as \`$TRASH_CMD_NAME\` itself, compatible to GNUs \`rmdir\`."
			echo "  see $RMDIR_CMD --help"
			echo "  see $TRASH_CMD --help"
			echo
			echo "Help options:"
			echo "      --help                display this help and exit"
			echo "      --version             output version information and exit"
			echo
			echo "Application options:"
			echo "      --ignore-fail-on-non-empty"
			echo "                 ignore each failure that is solely because a directory"
			echo "                    is non-empty"
			echo "  -p, --parents  remove DIRECTORY and its ancestors;"
			echo "                    e.g., \`$APP_NAME -p a/b/c\` is similar"
			echo "                    to \`$APP_NAME a/b/c a/b a\`"
			echo "  -v, --verbose  output a diagnostic for every directory processed"
			echo
			echo "Replacement option:"
			echo "  This option is not supposed to be used when calling $APP_NAME, it helps you"
			echo "  to control how and in which cases \`rm\` is replaced.  If you don't set this"
			echo "  option, root isn't treated specially."
			echo "      --forbid-root[=HOW]   forbid user root to trash directories.  When"
			echo "                              standard input is a terminal, 'ask-forbid' and"
			echo "                              'ask-pass' will question the user to pass the"
			echo "                              command to \`$RMDIR_CMD\`. When standard input is"
			echo "                              no terminal, 'ask-forbid' will abort the"
			echo "                              command, whereas 'ask-pass' will pass the"
			echo "                              command to \`$RMDIR_CMD\`. Use 'pass' to pass"
			echo "                              all commands of user root to \`$RMDIR_CMD\`. If"
			echo "                              user root should never trash directories, use"
			echo "                              'always'. In contrast, 'never' treats root in no"
			echo "                              special way. Without HOW, 'ask-forbid' is"
			echo "                              assumed"
			echo
			echo "To remove a directory whose name starts with a '-', for example '-foo', use"
			echo "one of these commands:"
			echo "  $APP_NAME -- -foo"
			echo "  $APP_NAME ./-foo"
			echo
			echo "See also \`trash-list\` (or \`list-trash\`), \`trash-empty\` (or \`empty-trash\`),"
			echo "\`trash-restore\` (or \`restore-trash\`), \`trash-rm\`, and the FreeDesktop.org"
			echo "Trash Specification at <http://www.ramendik.ru/docs/trashspec.html>."
			echo
			echo "Please report bugs using GitHub at <https://github.com/PhrozenByte/rmtrash>."
			echo "Besides, you will find general help and information about $APP_NAME there."
			exit 0
			;;

		"--version")
			echo "rmdirtrash $VERSION (build $BUILD)"
			echo "Copyright (C) 2011-2019 Daniel Rudolf"
			echo "License GPLv3: GNU GPL version 3 only <http://gnu.org/licenses/gpl.html>."
			echo "This is free software: you are free to change and redistribute it."
			echo "There is NO WARRANTY, to the extent permitted by law."
			echo
			echo "Written by Daniel Rudolf <https://www.daniel-rudolf.de/>"
			echo "See also: <https://github.com/PhrozenByte/rmtrash>"
			exit 0
			;;

		"--")
			shift
			break
			;;

		*)
			echo "$APP_NAME: execution of getopt failed" >&2
			showUsage
			exit 2
			;;
	esac
done

# no arguments given
if [ $# -eq 0 ]; then
	echo "$APP_NAME: too few arguments" >&2
	showUsage
	exit 2
fi

# forbid root?
if [ "$FORBID_ROOT" != "never" ] && [ "$(id -u)" -eq 0 ]; then
	echo "$APP_NAME: user root should never trash directories" >&2

	if [ "$FORBID_ROOT" == "always" ]; then
		exit 4096
	else
		PASS_COMMAND=false
		if [ "$FORBID_ROOT" == "ask-forbid" ] || [ "$FORBID_ROOT" == "ask-pass" ]; then
			# prompt
			if [ $SHELL_IN_INTERACTIVE_MODE == true ]; then
				echo -n "pass entire command to \`$RMDIR_CMD\` (delete arguments instead of trashing)? "
				read PASS_COMMAND_ANSWER

				if [ "$PASS_COMMAND_ANSWER" == "y" ] || [ "$PASS_COMMAND_ANSWER" == "yes" ]; then
					PASS_COMMAND=true
				fi
			else
				# shell is not running in interactive mode
				if [ "$FORBID_ROOT" == "ask-forbid" ]; then
					# unable to question user - forbid execution
					exit 4096
				else
					# unable to question user - pass command
					echo "$APP_NAME: entire command will be passed to \`$RMDIR_CMD\`..."
					PASS_COMMAND=true
				fi
			fi
		else
			# pass always
			PASS_COMMAND=true
			echo "$APP_NAME: entire command will be passed to \`$RMDIR_CMD\`..."
		fi

		if [ $PASS_COMMAND == true ]; then
			# create command
			CMD="$(getOptionsAsCmdString "$RMDIR_CMD")"

			CMD_ARGUMENTS=()
			while [ $# -gt 0 ]; do
				CMD_ARGUMENTS+=( "$1" )
				shift
			done

			# execute command
			if [ $VERBOSE == true ]; then
				echo "$APP_NAME: executing \`$CMD$(printf ' "%s"' "${CMD_ARGUMENTS[@]}")\`"
			fi

			eval "$CMD \"\${CMD_ARGUMENTS[@]}\""
			RMDIR_EXIT_STATUS=$?

			if [ "$RMDIR_EXIT_STATUS" -ne 0 ]; then
				echo "$APP_NAME: execution of \`$RMDIR_CMD\` failed (exit status $RMDIR_EXIT_STATUS)" >&2
				exit 16
			fi
			exit 0
		fi
	fi
fi

# handle each argument in a subprocess
EXIT=0
if [ $# -gt 1 ]; then
	# create command
	CMD="$(getOptionsAsCmdString "$0")"

	# parse arguments
	while [ $# -gt 1 ]; do
		# execute command
		eval "$CMD \"\$1\""

		# get return value
		EXIT_STATUS=$?
		if [ $EXIT_STATUS -ne 0 ]; then
			EXIT=$(( $EXIT | $EXIT_STATUS ))
		fi

		# process the next argument
		shift
	done
fi

# there's only one argument (left)
ARGUMENT="$1"

# remove trailing slash
let "ARGUMENT_LENGTH_TRAILING_SLASH_TEST_INDEX = ${#ARGUMENT} - 1"
if [ "${ARGUMENT:$ARGUMENT_LENGTH_TRAILING_SLASH_TEST_INDEX}" == "/" ]; then
	ARGUMENT="${1:0:$ARGUMENT_LENGTH_TRAILING_SLASH_TEST_INDEX}"
fi

# you can't remove . or ..
ARGUMENT_BASENAME="$(basename "$ARGUMENT")"
if [ "$ARGUMENT_BASENAME" == "." ] || [ "$ARGUMENT_BASENAME" == ".." ]; then
	echo "$APP_NAME: refusing to remove '.' or '..' directory: skipping '$ARGUMENT'" >&2
	exit 64
fi

# get full path
if [ "${ARGUMENT:0:1}" == "/" ]; then
	DIRECTORY="$ARGUMENT"
else
	DIRECTORY="$PWD/$ARGUMENT"
fi

# no such file or directory
if [ ! -h "$DIRECTORY" ] && [ ! -e "$DIRECTORY" ]; then
	echo "$APP_NAME: failed to remove '$ARGUMENT': No such file or directory" >&2
	exit 128
fi

# not a directory
if [ -h "$DIRECTORY" ] || [ ! -d "$DIRECTORY" ]; then
	echo "$APP_NAME: failed to remove '$ARGUMENT': Not a directory" >&2
	exit 256
fi

# directory not empty
if [ -z "$(find "$DIRECTORY" -maxdepth 0 -empty)" ] && [ $IGNORE_FAIL_ON_NON_EMPTY == false ]; then
	echo "$APP_NAME: failed to remove '$ARGUMENT': Directory not empty" >&2
	exit 512
fi

# okay, let's delete this directory
DELETE_DIRECTORIES[${#DELETE_DIRECTORIES[*]}]="$DIRECTORY"

# delete parent directories, too
if [ $PARENTS == true ]; then
	DELETE_PARENT_DIRECTORY_PREFIX=""
	if [ "${ARGUMENT:0:1}" != "/" ]; then
		DELETE_PARENT_DIRECTORY_PREFIX="$PWD"
	fi

	# get parent arguments
	while IFS="" read -r -u 4 -d $'\0' PARENT_ARGUMENT; do
		PARENT_ARGUMENTS[${#PARENT_ARGUMENTS[*]}]="$PARENT_ARGUMENT"
	done 4< <(echo "$ARGUMENT" | tr "/" "\0")

	# delete parent directories
	let "PARENT_ARGUMENTS_MAIN_INDEX = ${#PARENT_ARGUMENTS[*]} - 1"
	PARENT_ARGUMENTS_MAIN_MIN=0
	while [ $PARENT_ARGUMENTS_MAIN_INDEX -ge $PARENT_ARGUMENTS_MAIN_MIN ]; do
		PARENT_ARGUMENTS_INDEX=0
		PARENT_ARGUMENTS_MAX=$PARENT_ARGUMENTS_MAIN_INDEX

		# add parent directories to the path of the directory which should be deleted
		DELETE_PARENT_DIRECTORYNAME=""
		while [ $PARENT_ARGUMENTS_INDEX -le $PARENT_ARGUMENTS_MAX ]; do
			if [ "$DELETE_PARENT_DIRECTORYNAME" == "" ]; then
				DELETE_PARENT_DIRECTORYNAME="${PARENT_ARGUMENTS[$PARENT_ARGUMENTS_INDEX]}"
			else
				DELETE_PARENT_DIRECTORYNAME="$DELETE_PARENT_DIRECTORYNAME/${PARENT_ARGUMENTS[$PARENT_ARGUMENTS_INDEX]}"
			fi
			let "PARENT_ARGUMENTS_INDEX++"
		done
		DELETE_PARENT_DIRECTORY="$DELETE_PARENT_DIRECTORY_PREFIX/$DELETE_PARENT_DIRECTORYNAME"

		# get the directory that was deleted lastly
		let "LAST_DELETE_DIRECTORY_INDEX = ${#DELETE_DIRECTORIES[*]} - 1"
		LAST_DELETE_DIRECTORY="${DELETE_DIRECTORIES[$LAST_DELETE_DIRECTORY_INDEX]}"

		# directory not empty
		while IFS="" read -r -u 4 -d $'\0' DELETE_PARENT_DIRECTORY_CONTENT; do
			if [ "$DELETE_PARENT_DIRECTORY_CONTENT" != "$LAST_DELETE_DIRECTORY" ]; then
				break 2
			fi
		done 4< <(find "$DELETE_PARENT_DIRECTORY" -mindepth 1 -maxdepth 1 -print0)

		# okay, let's delete this directory instead of the inferior one
		DELETE_DIRECTORIES[$LAST_DELETE_DIRECTORY_INDEX]="$DELETE_PARENT_DIRECTORY"
		let "PARENT_ARGUMENTS_MAIN_INDEX--"
	done

	# clear list of parent directories
	unset PARENT_ARGUMENTS
fi

# create command
if [ ${#DELETE_DIRECTORIES[@]} -gt 0 ]; then
	CMD="$TRASH_CMD"
	if [ $VERBOSE == true ]; then
		CMD+=" --verbose"
	fi

	# add directories to command
	INDEX=0
	MAX=${#DELETE_DIRECTORIES[@]}
	CMD_ARGUMENTS=()
	while [ $INDEX -lt $MAX ]; do
		CMD_ARGUMENTS+=( "${DELETE_DIRECTORIES[$INDEX]}" )
		let "INDEX++"
	done

	# execute command
	if [ $VERBOSE == true ]; then
		echo "$APP_NAME: executing \`$CMD$(printf ' "%s"' "${CMD_ARGUMENTS[@]}")\`"
	fi
	STDOUT="$(eval "$CMD \"\${CMD_ARGUMENTS[@]}\"" 2>&1)"

	# remove traceback from stdout
	if [ -n "$STDOUT" ]; then
		STDOUT_OLD="$STDOUT"
		STDOUT=""

		IFS=$'\n'
		IS_TRACEBACK=false
		for STDOUT_LINE in $STDOUT_OLD; do
			if [ -n "$STDOUT" ]; then
				n=$'\n'
			fi

			if [ "$STDOUT_LINE" == "Traceback (most recent call last):" ]; then
				IS_TRACEBACK=true
				continue
			fi

			if [ $IS_TRACEBACK == true ]; then
				if [ "${STDOUT_LINE:0:2}" != "  " ]; then
					STDOUT+="$n$STDOUT_LINE"
					IS_TRACEBACK=false
				fi
			else
				STDOUT+="$n$STDOUT_LINE"
			fi
		done
	fi

	# catch some special errors
	if [ -n "$STDOUT" ]; then
		# unable to create trashcan (permission denied)
		while true; do
			# get insufficient trashcan
			INSUFFICIENT_TRASHCAN="$(echo "$STDOUT" | grep -m 1 -b -oP "(?<=^OSError: \[Errno 13\] Permission denied: ')([^\0]*?)/\.Trash-([0-9]+?)(?='$)")"
			if [ -n "$INSUFFICIENT_TRASHCAN" ]; then
				TEMP="$(echo "$INSUFFICIENT_TRASHCAN" | grep -m 1 -oP "^([0-9]+?)(?=:)")"

				# fix insufficient trashcan string
				let "INSUFFICIENT_TRASHCAN_INDEX = ${#TEMP} + 1"
				INSUFFICIENT_TRASHCAN="${INSUFFICIENT_TRASHCAN:$INSUFFICIENT_TRASHCAN_INDEX}"

				# remove that error from stdout
				let "STDOUT_PREFIX_LENGTH = $TEMP - 40"
				let "STDOUT_SUFFIX_INDEX = $TEMP + ${#INSUFFICIENT_TRASHCAN} + 1 + 1"
				STDOUT="${STDOUT:0:$STDOUT_PREFIX_LENGTH}${STDOUT:$STDOUT_SUFFIX_INDEX}"

				# check if insufficient trashcan is valid
				if [ "$(basename "$INSUFFICIENT_TRASHCAN")" != ".Trash-$(id -u)" ]; then
					continue
				fi

				# get delete argument
				DELETE_ARGUMENT="$(echo "$STDOUT" | grep -m 1 -b -oP "(?<=^trash: cannot trash \`)([^\0]+?)(?=': \[Errno 13\] Permission denied: '$INSUFFICIENT_TRASHCAN'$)")"
				if [ -n "$DELETE_ARGUMENT" ]; then
					TEMP="$(echo "$DELETE_ARGUMENT" | grep -m 1 -oP "^([0-9]+?)(?=:)")"

					# fix delete argument string
					let "DELETE_ARGUMENT_INDEX = ${#TEMP} + 1"
					DELETE_ARGUMENT="${DELETE_ARGUMENT:$DELETE_ARGUMENT_INDEX}"

					# remove that error from stdout
					let "STDOUT_PREFIX_LENGTH = $TEMP - 21"
					let "STDOUT_SUFFIX_INDEX = $TEMP + ${#DELETE_ARGUMENT} + 34 + ${#INSUFFICIENT_TRASHCAN} + 1 + 1"
					STDOUT="${STDOUT:0:$STDOUT_PREFIX_LENGTH}${STDOUT:$STDOUT_SUFFIX_INDEX}"

					# output what actually happened
					echo "$APP_NAME: cannot remove '$DELETE_ARGUMENT': unable to create trashcan '$INSUFFICIENT_TRASHCAN': Permission denied" >&2
					EXIT=$(( $EXIT | 1024 ))

					# recommend user to create a trashcan-base-directory
					INSUFFICIENT_TRASHCAN_BASE_DIRECTORY="$(echo "$INSUFFICIENT_TRASHCAN" | grep -m 1 -oP "^([^\0]+?).Trash(?=-$(id -u)$)")"

					echo "" >&2
					echo "According to the FreeDesktop.org Trash Specification, it's recommended to" >&2
					echo "create a directory where all users can create a trashcan on their own." >&2
					echo "You can catch up on that by typing:" >&2
					echo -e "\tsudo mkdir -p \"$INSUFFICIENT_TRASHCAN_BASE_DIRECTORY\"" >&2
					echo -e "\tsudo chmod 1777 \"$INSUFFICIENT_TRASHCAN_BASE_DIRECTORY\"" >&2
					echo "When you've done that, repeat your deletion-command. Alternatively" >&2
					echo "you can delete the argument instead of trashing it." >&2
					echo "" >&2

					# ask to pass argument to rm if shell is in interactive mode
					if [ $SHELL_IN_INTERACTIVE_MODE == true ]; then
						echo -n "pass argument to \`$RMDIR_CMD\` (delete argument instead of trashing)? "
						read PASS_COMMAND_ANSWER

						if [ "$PASS_COMMAND_ANSWER" == "y" ] || [ "$PASS_COMMAND_ANSWER" == "yes" ]; then
							CMD="$(getOptionsAsCmdString "$RMDIR_CMD")"

							if [ $VERBOSE == true ]; then
								echo "$APP_NAME: executing \`$CMD \"$DELETE_ARGUMENT\"\`"
							fi

							eval "$CMD \"\$DELETE_ARGUMENT\""
							RMDIR_EXIT_STATUS=$?

							if [ "$RMDIR_EXIT_STATUS" -ne 0 ]; then
								EXIT=$(( $EXIT | 16 ))
								echo "$APP_NAME: execution of \`$RMDIR_CMD\` failed (exit status $RMDIR_EXIT_STATUS)" >&2
							fi
						fi
					fi
				fi

			# no more errors
			else
				break
			fi
		done

		# you can't trash the trashcan
		while true; do
			# get delete argument
			DELETE_ARGUMENT="$(echo "$STDOUT" | grep -m 1 -b -oP "(?<=^shutil\.Error: Cannot move a directory ')([^\0]+?)(?=' into itself '([^\0]+?)'\.$)")"
			if [ -n "$DELETE_ARGUMENT" ]; then
				TEMP="$(echo "$DELETE_ARGUMENT" | grep -m 1 -oP "^([0-9]+?)(?=:)")"

				# fix delete argument string
				let "DELETE_ARGUMENT_INDEX = ${#TEMP} + 1"
				DELETE_ARGUMENT="${DELETE_ARGUMENT:$DELETE_ARGUMENT_INDEX}"

				# get delete argument target
				DELETE_ARGUMENT_TARGET="$(echo "$STDOUT" | grep -m 1 -oP "(?<=^shutil\.Error: Cannot move a directory '$DELETE_ARGUMENT' into itself ')([^\0]+?)(?='\.$)")"

				# remove that error from stdout
				let "STDOUT_PREFIX_LENGTH = $TEMP - 39"
				let "STDOUT_SUFFIX_INDEX = $TEMP + ${#DELETE_ARGUMENT} + 15 + ${#DELETE_ARGUMENT_TARGET} + 2 + 1"
				STDOUT="${STDOUT:0:$STDOUT_PREFIX_LENGTH}${STDOUT:$STDOUT_SUFFIX_INDEX}"

				# output what actually happened
				echo "$APP_NAME: cannot remove '$DELETE_ARGUMENT': you can't trash the trashcan" >&2
				EXIT=$(( $EXIT | 2048 ))

				# ask to pass argument to rm if shell is in interactive mode
				if [ $SHELL_IN_INTERACTIVE_MODE == true ]; then
					echo -n "pass argument to \`$RMDIR_CMD\` (delete argument instead of trashing)? "
					read PASS_COMMAND_ANSWER

					if [ "$PASS_COMMAND_ANSWER" == "y" ] || [ "$PASS_COMMAND_ANSWER" == "yes" ]; then
						CMD="$(getOptionsAsCmdString "$RMDIR_CMD")"

						if [ $VERBOSE == true ]; then
							echo "$APP_NAME: executing \`$CMD \"$DELETE_ARGUMENT\"\`"
						fi

						eval "$CMD \"\$DELETE_ARGUMENT\""
						RMDIR_EXIT_STATUS=$?

						if [ "$RMDIR_EXIT_STATUS" -ne 0 ]; then
							EXIT=$(( $EXIT | 16 ))
							echo "$APP_NAME: execution of \`$RMDIR_CMD\` failed (exit status $RMDIR_EXIT_STATUS)" >&2
						fi
					fi
				fi

			# no more errors
			else
				break
			fi
		done
	fi

	# output stdout
	if [ -n "$STDOUT" ]; then
		echo "$STDOUT"
	fi
fi

exit $EXIT
